package com.lambda.swipeback;

import android.content.Context;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;

import androidx.core.widget.ScrollerCompat;

import java.util.Arrays;

public class WxViewDragHelper {
    public static final int INVALID_POINTER = -1;
    public static final int STATE_IDLE = 0;
    public static final int STATE_DRAGGING = 1;
    public static final int STATE_SETTLING = 2;

    public static final int EDGE_LEFT = 1;
    public static final int EDGE_RIGHT = 1 << 1;
    public static final int EDGE_TOP = 1 << 2;
    public static final int EDGE_BOTTOM = 1 << 3;

    private static final int MAX_SETTLE_DURATION = 600; // ms

    public int mPointersDown;
    public int[] mInitialEdgesTouched;
    public float[] mInitialMotionX;
    public float[] mInitialMotionY;
    public float[] mLastMotionX;
    public float[] mLastMotionY;
    private int[] mEdgeDragsInProgress;
    private int[] mEdgeDragsLocked;
    public int mState;
    public final ViewGroup mParentView;
    public final Callback mCallback;
    public float mMaxVelocity;
    public float mMinVelocity;
    private int mEdgeSize;
    public ScrollerCompat mScroller;
    public int mActivePointerId = -1;
    private int mTouchSlop;
    public VelocityTracker mVelocityTracker;
    public int mTrackingEdges;
    public View mCapturedView;
    public boolean mReleaseInProgress;

    public final Runnable mSetIdleRunnable = new Runnable() {
        public final void run() {
            setDragState(STATE_IDLE);
        }
    };

    public static abstract class Callback {
        public abstract boolean tryCaptureView(int edgeFlags);

        public void onViewDragStateChanged(int state) {
        }

        public void onViewPositionChanged(int edgeFlags, int pointerId) {
        }

        public void onViewReleased(View view, float xvel, float yvel) {
        }

        public int getViewHorizontalDragRange() {
            return 0;
        }

        public int clampViewPositionHorizontal(View view, int left) {
            return 0;
        }
    }

    private WxViewDragHelper(Context context, ViewGroup viewGroup, Callback callback, Interpolator interpolator) {
        if (viewGroup == null) throw new IllegalArgumentException("Parent view may not be null");
        if (callback == null) throw new IllegalArgumentException("Callback may not be null");

        mParentView = viewGroup;
        mCallback = callback;
        ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
        mEdgeSize = (int) ((context.getResources().getDisplayMetrics().density * 20.0f) + 0.5f);
        mTouchSlop = viewConfiguration.getScaledTouchSlop();
        mMaxVelocity = (float) viewConfiguration.getScaledMaximumFlingVelocity();
        mMinVelocity = (float) viewConfiguration.getScaledMinimumFlingVelocity();
        mScroller = ScrollerCompat.create(context, interpolator);
    }

    public final View findTopChildUnder(int x, int y) {
        int childCount = mParentView.getChildCount() - 1;
        while (childCount >= 0) {
            View childAt = mParentView.getChildAt(childCount);
            if (x < childAt.getLeft() || x >= childAt.getRight() || y < childAt.getTop() || y >= childAt.getBottom()) {
                childCount--;
            } else {
                return childAt;
            }
        }
        return null;
    }

    public final void cancel() {
        mActivePointerId = INVALID_POINTER;
        if (mInitialMotionX != null) {
            Arrays.fill(mInitialMotionX, 0.0f);
            Arrays.fill(mInitialMotionY, 0.0f);
            Arrays.fill(mLastMotionX, 0.0f);
            Arrays.fill(mLastMotionY, 0.0f);
            Arrays.fill(mInitialEdgesTouched, 0);
            Arrays.fill(mEdgeDragsInProgress, 0);
            Arrays.fill(mEdgeDragsLocked, 0);
            mPointersDown = 0;
        }
        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    final void setDragState(int state) {
        if (mState != state) {
            mState = state;
            mCallback.onViewDragStateChanged(state);
            if (state == 0) {
                mCapturedView = null;
            }
        }
    }

    public final boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
        if (toCapture == mCapturedView && mActivePointerId == pointerId) return true;

        if (toCapture != null && mCallback.tryCaptureView(pointerId)) {
            mActivePointerId = pointerId;
            captureChildView(toCapture, pointerId);
            return true;
        }
        return false;
    }

    public void captureChildView(View childView, int activePointerId) {
        if (childView.getParent() != mParentView) {
            throw new IllegalArgumentException("captureChildView: parameter must be a descendant "
                    + "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
        }

        mCapturedView = childView;
        mActivePointerId = activePointerId;
        setDragState(STATE_DRAGGING);
    }

    private static float clampMag(float value, float absMin, float absMax) {
        final float absValue = Math.abs(value);
        if (absValue < absMin) return 0;
        if (absValue > absMax) return value > 0 ? absMax : -absMax;
        return value;
    }

    private static int clampMag(int value, int absMin, int absMax) {
        final int absValue = Math.abs(value);
        if (absValue < absMin) return 0;
        if (absValue > absMax) return value > 0 ? absMax : -absMax;
        return value;
    }

    public final void dispatchViewReleased(float xvel, float yvel) {
        mReleaseInProgress = true;
        mCallback.onViewReleased(mCapturedView, xvel, yvel);
        mReleaseInProgress = false;
        if (mState == STATE_DRAGGING) {
            setDragState(STATE_IDLE);
        }
    }

    public final void releaseViewForPointerUp() {
        mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
        final float xvel = clampMag(
                mVelocityTracker.getXVelocity(mActivePointerId),
                mMinVelocity, mMaxVelocity
        );
        final float yvel = clampMag(
                mVelocityTracker.getYVelocity(mActivePointerId),
                mMinVelocity, mMaxVelocity
        );
        dispatchViewReleased(xvel, yvel);
    }

    private void ensureMotionHistorySizeForId(int pointerId) {
        if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) {
            float[] imx = new float[(pointerId + 1)];
            float[] imy = new float[(pointerId + 1)];
            float[] lmx = new float[(pointerId + 1)];
            float[] lmy = new float[(pointerId + 1)];
            int[] iit = new int[(pointerId + 1)];
            int[] edip = new int[(pointerId + 1)];
            int[] edl = new int[(pointerId + 1)];

            if (mInitialMotionX != null) {
                System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length);
                System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length);
                System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length);
                System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length);
                System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length);
                System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length);
                System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length);
            }
            mInitialMotionX = imx;
            mInitialMotionY = imy;
            mLastMotionX = lmx;
            mLastMotionY = lmy;
            mInitialEdgesTouched = iit;
            mEdgeDragsInProgress = edip;
            mEdgeDragsLocked = edl;
        }
    }

    private int getEdgesTouched(int x, int y) {
        int result = 0;
        if (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT;
        if (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP;
        if (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT;
        if (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM;
        return result;
    }

    public final void saveInitialMotion(float x, float y, int pointerId) {
        ensureMotionHistorySizeForId(pointerId);
        mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
        mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
        mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y);
        mPointersDown |= 1 << pointerId;
    }

    private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) {
        final float absDelta = Math.abs(delta);
        final float absODelta = Math.abs(odelta);

        if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0
                || (mEdgeDragsLocked[pointerId] & edge) == edge
                || (mEdgeDragsInProgress[pointerId] & edge) == edge
                || (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {
            return false;
        }

        return (mEdgeDragsInProgress[pointerId] & edge) == 0 && !(absDelta <= mTouchSlop);
    }

    public final void saveLastMotion(MotionEvent motionEvent) {
        int pointerCount = motionEvent.getPointerCount();
        for (int i = 0; i < pointerCount; i++) {
            int pointerId = motionEvent.getPointerId(i);
            float x = motionEvent.getX(i);
            float y = motionEvent.getY(i);
            this.mLastMotionX[pointerId] = x;
            this.mLastMotionY[pointerId] = y;
        }
    }

    public final void reportNewEdgeDrags(float dx, float dy, int pointerId) {
        int dragsStarted = 0;
        if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) {
            dragsStarted |= EDGE_LEFT;
        }
        if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) {
            dragsStarted |= EDGE_TOP;
        }
        if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) {
            dragsStarted |= EDGE_RIGHT;
        }
        if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {
            dragsStarted |= EDGE_BOTTOM;
        }

        if (dragsStarted != 0) {
            mEdgeDragsInProgress[pointerId] |= dragsStarted;
        }
    }

    public final boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel, long duration) {
        int startLeft = mCapturedView.getLeft();
        int startTop = mCapturedView.getTop();
        int dx = finalLeft - startLeft;
        int dy = finalTop - startTop;
        if (dx == 0 && dy == 0) {
            mScroller.abortAnimation();
            setDragState(STATE_IDLE);
            return false;
        }
        if (duration == 0) {
            duration = computeSettleDuration(dx, dy, xvel, yvel);
        }
        ad.d("WxViewDragHelper", "ashutest:: xvel %d, yvel %d, dx %d, dy %d duration %d", xvel, yvel, dx, dy, duration);
        mScroller.startScroll(startLeft, startTop, dx, dy, (int) duration);

        setDragState(STATE_SETTLING);
        return true;
    }

    private int computeSettleDuration(int dx, int dy, int xvel, int yvel) {
        xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity);
        yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity);
        int absDx = Math.abs(dx);
        int absDy = Math.abs(dy);
        int absXVel = Math.abs(xvel);
        int absYVel = Math.abs(yvel);
        int addedVel = absXVel + absYVel;
        int addedDistance = absDx + absDy;

        float xweight = xvel != 0 ? ((float) absXVel) / ((float) addedVel) : ((float) absDx) / ((float) addedDistance);
        float yweight = yvel != 0 ? ((float) absYVel) / ((float) addedVel) : ((float) absDy) / ((float) addedDistance);

        int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange());
        int yduration = computeAxisDuration(dy, yvel, 0);

        return (int) (xweight * xduration + yweight * yduration);
    }

    private int computeAxisDuration(int delta, int velocity, int motionRange) {
        if (delta == 0) return 0;

        int width = mParentView.getWidth();
        int halfWidth = width / 2;
        float distanceRatio = Math.min(1.0f, (float) Math.abs(delta) / width);
        float distance = distanceInfluenceForSnapDuration(distanceRatio) * halfWidth + halfWidth;

        int duration;
        velocity = Math.abs(velocity);
        if (velocity > 0) {
            duration = Math.round(Math.abs(distance / velocity) * 1000) * 4;
        } else {
            float range = ((float) Math.abs(delta)) / ((float) motionRange);
            duration = (int) ((range + 1.0f) * 256.0f);
        }
        return Math.min(duration, MAX_SETTLE_DURATION);
    }

    private static float distanceInfluenceForSnapDuration(float f) {
        f -= 0.5f; // center the values about 0.
        f *= 0.3f * (float) Math.PI / 2.0f;
        return (float) Math.sin(f);
    }

    public final boolean checkTouchSlop(View view, float dx) {
        if (view == null) return false;

        boolean checkHorizontal = mCallback.getViewHorizontalDragRange() > 0;

        if (checkHorizontal) {
            return Math.abs(dx) > mTouchSlop;
        }
        return false;
    }

    public final void clearMotionHistory(int pointerId) {
        if (mInitialMotionX == null || !isPointerDown(pointerId)) return;

        mInitialMotionX[pointerId] = 0.0f;
        mInitialMotionY[pointerId] = 0.0f;
        mLastMotionX[pointerId] = 0.0f;
        mLastMotionY[pointerId] = 0.0f;
        mInitialEdgesTouched[pointerId] = 0;
        mEdgeDragsInProgress[pointerId] = 0;
        mEdgeDragsLocked[pointerId] = 0;
        mPointersDown &= ~(1 << pointerId);
    }

    public boolean isPointerDown(int pointerId) {
        return (mPointersDown & 1 << pointerId) != 0;
    }

    public static WxViewDragHelper create(ViewGroup viewGroup, Callback aVar, Interpolator interpolator) {
        return new WxViewDragHelper(viewGroup.getContext(), viewGroup, aVar, interpolator);
    }
}
